package com.multiplay;

import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

import android.net.wifi.WifiManager;
import android.util.Log;

public class Discoverer extends WiFiLoop implements Runnable 
{
    private DatagramChannel broadcast_channel = null;
    private InetSocketAddress bcast;
    private boolean send_discover_packet = true;
    private SocketChannel cli_channel = null;

    public Discoverer(Object p) throws IOException
    {
        super(p);
    }

    public boolean init(WifiManager wifi_mng) throws IOException
    {
        boolean rc = true;

        rc = super.init(wifi_mng);
        if (!rc)
        {
            Log.e(GlobalDefs.LOG_APP_TAG, "WIFI not enabled");            
            return false;
        }

        broadcast_channel = DatagramChannel.open();
        broadcast_channel.configureBlocking(false);

        InetAddress bcast_addr = get_broadcast_address();
        Log.v(GlobalDefs.LOG_APP_TAG, "Broadcast address: " + 
            bcast_addr.getHostAddress());

        bcast = new InetSocketAddress(bcast_addr, GlobalDefs.BROADCAST_PORT);

        DatagramSocket broadcast_socket = broadcast_channel.socket();
        InetSocketAddress addr = new InetSocketAddress(GlobalDefs.ANSWER_UDP_PORT);
        broadcast_socket.bind(addr);
        broadcast_socket.setBroadcast(true);

        broadcast_channel.register(selector, SelectionKey.OP_READ, null);

        return rc;
    }

    private void send_discover_packet() throws IOException
    {
        if (!send_discover_packet)
            return;

        int sign = GlobalDefs.DISCOVERER_SIGNATURE;

        ByteBuffer data = ByteBuffer.allocate(8);
        IntBuffer t = data.asIntBuffer();
        t.put(sign);
        t.put(ip_address);
        int len = broadcast_channel.send(data, bcast);
        Log.v(GlobalDefs.LOG_APP_TAG, "Broadcast sent. len = " + len);
    }

    private InetAddress get_broadcast_address() throws IOException 
    {
        int broadcast = (ip_address & netmask) | ~netmask;
        byte [] quads = WiFiLoop.ip_addr_int2bytes(broadcast);
        
        return InetAddress.getByAddress(quads);
    }

    private void cleanup()
    {
        try 
        {
            selector.close();
            if (broadcast_channel != null)
                broadcast_channel.close();
        } 
        catch (IOException e) 
        {
            Log.e(GlobalDefs.LOG_APP_TAG, e.getMessage());
        }
    }

    public void run() 
    {
        int channels;

        Log.v(GlobalDefs.LOG_APP_TAG, "Start discoverer thread");

        while (!is_stopping())
        {
            try 
            {
                channels = selector.select(GlobalDefs.DISCOVERER_TIMEOUT);
                if (channels == 0)
                {
                    send_discover_packet();
                    continue;
                }
                SelectionKey key = null;
                Iterator<SelectionKey> it = selector.selectedKeys().iterator();

                while (it.hasNext())
                {
                    Log.v(GlobalDefs.LOG_APP_TAG, "Got selected key");

                    key = it.next();
                    it.remove();
                    
                    if (key.isReadable())
                    {
                        Log.v(GlobalDefs.LOG_APP_TAG, "Got answer");

                        ByteBuffer data = ByteBuffer.allocate(64);
                        broadcast_channel.receive(data);
                        Log.v(GlobalDefs.LOG_APP_TAG, "Read bytes: " + data.position());
                        int srv_addr = 0;
                        if (data.position() == 8)
                        {
                            data.position(0);
                            IntBuffer t = data.asIntBuffer();
                            int sign = t.get();
                            srv_addr = t.get();
                            Log.v(GlobalDefs.LOG_APP_TAG, "Sign: " + sign + "ip: " + srv_addr);
                        }

                        if (srv_addr != 0)
                        {
                            byte [] quads = WiFiLoop.ip_addr_int2bytes(srv_addr);
                            InetAddress ia = InetAddress.getByAddress(quads);
                            InetSocketAddress saddr = new InetSocketAddress(ia, GlobalDefs.SERVER_TCP_PORT);

                            Log.v(GlobalDefs.LOG_APP_TAG, "Try connect to: " + saddr.getHostName());

                            cli_channel = SocketChannel.open();
                            cli_channel.configureBlocking(false);
                            cli_channel.register(selector, SelectionKey.OP_CONNECT /*| SelectionKey.OP_READ*/, null);

                            Log.v(GlobalDefs.LOG_APP_TAG, "Sending connect");
                            cli_channel.connect(saddr);
                            Log.v(GlobalDefs.LOG_APP_TAG, "Sent connect");

                            send_discover_packet = false;
                            broadcast_channel.close();
                            broadcast_channel = null;

                            key.cancel();
                            continue;
                        }
                    }

                    if (key.isConnectable())
                    {
                        Log.v(GlobalDefs.LOG_APP_TAG, "Client connected");
                        cli_channel.finishConnect();
                        //cli_channel.close();
                        //cli_channel = null;
                        key.cancel();
                    }
                }
            } 
            catch (IOException e)
            {
                stop = true;
                Log.e(GlobalDefs.LOG_APP_TAG, e.getMessage());
            }
        }

        cleanup();
        Log.v(GlobalDefs.LOG_APP_TAG, "Finish discoverer thread");
    }
}
